import Bench2.Coroutines.go
import Bench2.Coroutines.loop
import Bench2.Coroutines.pause
import Bench2.Coroutines.resume
import kotlin.coroutines.Continuation
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.resume

object Bench2 {
    object Coroutines {
        private val waitingCor: ArrayList<Continuation<Unit>> = ArrayList(1_000_000)

        fun loop() {
            while (true)
                (waitingCor.removeLastOrNull() ?: break).resume(Unit)
        }

        fun resume(c: Continuation<Unit>) {
            waitingCor.add(c)
        }

        fun go(f: suspend () -> Unit) {
            resume(f.createCoroutineUnintercepted(Continuation(EmptyCoroutineContext) {}))
        }

        suspend fun pause(f: (Continuation<Unit>) -> Unit) {
            suspendCoroutineUninterceptedOrReturn {
                f.invoke(it)
                COROUTINE_SUSPENDED
            }
        }
    }

    class IntChan {
        private var value: Int = 0
        private var hasValue: Boolean = false
        private val putWaitings: ArrayList<Continuation<Unit>> = ArrayList()
        private val getWaitings: ArrayList<Continuation<Unit>> = ArrayList()

        suspend fun put(v: Int) {
            while (hasValue)
                pause(putWaitings::add)
            value = v
            hasValue = true
            val c = getWaitings.removeLastOrNull()
            if (c != null)
                resume(c)
        }

        suspend fun get(): Int {
            while (!hasValue)
                pause(getWaitings::add)
            hasValue = false
            val c = putWaitings.removeLastOrNull()
            if (c != null)
                resume(c)
            return value
        }
    }

    private suspend fun f(output: IntChan, input: IntChan) {
        output.put(1 + input.get())
    }

    private const val maxCount: Int = 1_000_000

    private suspend fun test() {
        println("Started, sending $maxCount messages.")
        println()
        val t = System.currentTimeMillis()
        try {
            val finalOutput = IntChan()
            var r = finalOutput
            for (i in 0 until maxCount) {
                val left = r
                val right = IntChan()
                r = right
                go { f(left, right) }
            }
            r.put(0)
            println(finalOutput.get())
        } finally {
            println("Sending $maxCount messages took ${System.currentTimeMillis() - t} ms")
        }
    }

    fun main() {
        go {
            test()
            test()
        }
        go {
            test()
            test()
        }
        go {
            test()
            test()
        }
        loop()
    }
}

fun main() {
    Bench2.main()
}
